#include "snake.h"
#include <stdio.h>
#include <stdlib.h>


#define KEY_PRESS(VK) (GetAsyncKeyState(VK) & 0x01 ? 1 : 0)
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define POS_X 6
#define POS_Y 6

// ↑↓←→●□★ 

// 贪吃蛇游戏结构体初始化
void SnakeInit(pSnake ps)
{
    ps->_pSnake = NULL;
    ps->_pFood = NULL;
    ps->_foodScore = 10;
    ps->_totalScore = 0;
    ps->_status = OK;
    ps->_dir = RIGHT;
    ps->_sleepTime = 200;
}

// 设置控制台窗口大小和名称
void CmdInit(void)
{
    // 设置控制台窗口大小 100列 30行
    system("mode con cols=100 lines=30");
    // 设置控制台名称
    system("title snake");
}

// 光标隐藏
void CursorHide(void)
{
    // 获取句柄
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    // 光标信息
    CONSOLE_CURSOR_INFO cursor_info = {0};
    // 获取光标信息
    GetConsoleCursorInfo(handle, &cursor_info);
    // 设置光标属性
    cursor_info.dwSize = 10;
    cursor_info.bVisible = 0;
    SetConsoleCursorInfo(handle, &cursor_info);
}

// 设置光标位置
void SetPos(int x, int y)
{
    // 获取句柄
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    // 设置光标位置
    COORD pos = {x, y};
    SetConsoleCursorPosition(handle, pos);
}


// 使用--,|,/ 绘制英文SNAKE
/* 
           _____                  _                 _____
           |    \   |\_    |     / \      |   _/   |
           |_____   |  \   |    /   \     |__/     |_____
                |   |   \_ |   /_____\    |  \_    |
           \____|   |     \|  /       \   |    \   |_____
 */
void PrintSnake(void)
{
    SetPos(32, 5);
    printf(" _____                  _                 _____");
    SetPos(32, 6);
    printf(" |    \\   |\\_    |     / \\      |   _/   |");
    SetPos(32, 7);
    printf(" |_____   |  \\   |    /   \\     |__/     |_____");
    SetPos(32, 8);
    printf("      |   |   \\_ |   /_____\\    |  \\_    |");
    SetPos(32, 9);
    printf(" \\____|   |     \\|  /       \\   |    \\   |_____");
}

// 打印欢迎信息
void WelcomeToGame(void)
{
    PrintSnake();
    // 暂停
    SetPos(45, 20);
    system("pause");
}

// 游戏介绍
void GameIntroduction(void)
{
    // 清屏
    system("cls");
    SetPos(45, 10);
    wprintf(L"游戏介绍:");
    SetPos(45, 12);
    wprintf(L"1. 使用↑,↓,←,→控制蛇的移动");
    SetPos(45, 14);
    wprintf(L"2. 吃到食物蛇的长度加1");
    SetPos(45, 16);
    wprintf(L"2. F3加速, F4减速");
    SetPos(45, 18);
    wprintf(L"3. 空格暂停");
    SetPos(45, 20);
    wprintf(L"4. Esc退出游戏");
    // 暂停
    SetPos(45, 22);
    system("pause");
}

// 地图绘制 
// 上(0,0) - (56,0)
// 下(0,26) - (56,26)
// 左(0,0) - (0,26)
// 右(56,0) - (56,26)
// 注意打印的是宽字符 占两个x坐标 因此左右打印的时候要每打印一个x坐标加2
void MapDraw(void)
{   
    // 清屏
    system("cls");
    // 上墙
    for (int i = 0; i < 57; i+=2)
    {
        SetPos(i, 0);
        wprintf(L"%c", WALL);
    }
    // 下墙
    for (int i = 0; i < 57; i+=2)
    {
        SetPos(i, 26);
        wprintf(L"%c", WALL);
    }
    // 最上面和最下面已经打印过了
    // 左墙
    for (int i = 1; i < 26; i++)
    {
        SetPos(0, i);
        wprintf(L"%c", WALL);
    }
    // 右墙
    for (int i = 1; i < 26; i++)
    {
        SetPos(56, i);
        wprintf(L"%c", WALL);
    } 
}

// 打印静态帮助信息
// 单个食物分数,总分数的名称
// 操作说明
void PrintStaticHelp(void)
{
    SetPos(70, 5);
    wprintf(L"单个食物分数: 10");
    SetPos(70, 7);
    wprintf(L"总分数:        0");
    SetPos(70, 9);
    wprintf(L"操作说明:");
    SetPos(70, 11);
    wprintf(L"↑ : 上移");
    SetPos(70, 13);
    wprintf(L"↓ : 下移");
    SetPos(70, 15);
    wprintf(L"← : 左移");
    SetPos(70, 17);
    wprintf(L"→ : 右移");
    SetPos(70, 19);
    wprintf(L"F3: 加速");
    SetPos(70, 21);
    wprintf(L"F4: 减速");
    SetPos(70, 23);
    wprintf(L"空格: 暂停");
    SetPos(70, 25);
    wprintf(L"Esc: 退出");
}

// 蛇初始化
// 在地图的(6,6)位置初始化蛇
// 蛇的长度为5
// 使用头插法
void InitSnake(pSnake ps)
{
    pSnakeNode cur = NULL;
    int i = 0;
    // 创建蛇身节点 并初始化坐标    
    for (i = 0; i < 5; i++)
    {
        pSnakeNode node = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (node == NULL)
        {
            perror("pSnakeNode malloc failed");
            exit(1);
        }
        node->x = POS_X + i * 2;
        node->y = POS_Y;
        node->next = NULL;
        // 头插法
        if (ps->_pSnake == NULL)
        {
            ps->_pSnake = node;
        }
        else
        {
            node->next = ps->_pSnake;
            ps->_pSnake = node;
        }
    }
    // 打印蛇
    cur = ps->_pSnake;
    while (cur != NULL)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }
}


// 创建食物节点
void CreatFood(pSnake ps)
{
    // 申请节点空间
    pSnakeNode foodnode = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (foodnode == NULL)
    {
        perror("foodnode malloc failed");
        exit(1);
    }
    foodnode->next = NULL;
    // 随机生成食物坐标 在墙范围内但是不能在蛇身上 而且x坐标是偶数
    while (1)
    {
        int x = rand() % 54 + 2;
        int y = rand() % 24 + 2;
        pSnakeNode cur = ps->_pSnake;
        while (cur != NULL)
        {
            if (cur->x == x && cur->y == y || x % 2 != 0)
            {
                break;
            }
            cur = cur->next;
        }
        if (cur == NULL)
        {
            foodnode->x = x;
            foodnode->y = y;
            break;
        }
    }
    // 打印食物
    SetPos(foodnode->x, foodnode->y);
    wprintf(L"%c", FOOD);
    ps->_pFood = foodnode;
}

// 撞墙检测
// 撞墙返回1 否则返回0
int IsKillByWall(pSnake ps)
{
    if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
    {
        return 1;
    }
    return 0;
}

// 撞自己检测
// 撞自己返回1 否则返回0
int IsKillBySelf(pSnake ps)
{
    pSnakeNode cur = ps->_pSnake->next;
    // 从第二个节点开始遍历 并判断是否和蛇头坐标相同
    while (cur != NULL)
    {
        if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
        {
            return 1;
        }
        cur = cur->next;
    }
    return 0;
}

// 吃掉食物后的处理
void EatFood(pSnake ps)
{
    // 加分
    ps->_totalScore += ps->_foodScore;
    // 打印总分数
    SetPos(82, 7);
    printf("%4d", ps->_totalScore);
    // 是 头插食物节点 创建新节点
    ps->_pFood->next = ps->_pSnake;
    ps->_pSnake = ps->_pFood;
    // 打印新蛇头
    SetPos(ps->_pSnake->x, ps->_pSnake->y);
    wprintf(L"%c", BODY);
    // 创建新食物
    CreatFood(ps);
}


// 没有迟到食物的处理
void NotEatFood(pSnake ps, int x, int y)
{
    // 创建新节点并头插
    pSnakeNode node = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (node == NULL)
    {
        perror("node malloc failed");
        exit(1);
    }
    node->x = x;
    node->y = y;
    node->next = ps->_pSnake;
    ps->_pSnake = node;
    // 打印新蛇 顺便删除尾节点 释放空间 打印空格
    pSnakeNode cur = ps->_pSnake;
    while (cur->next->next != NULL)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }
    SetPos(cur->x, cur->y);
    wprintf(L"%c", BODY);
    SetPos(cur->next->x, cur->next->y);
    wprintf(L"  ");
    free(cur->next);
    cur->next = NULL;
}


// 蛇的移动 -- 主游戏程序 在此循环
// 吃到食物 创建新的食物节点
// 没有吃到食物头插新节点删除尾节点并释放空间
// 减速睡眠时间-30 睡眠时间最少减4次 单个食物分数加2
// 加速睡眠时间+30 睡眠时间最多加4次 单个食物分数减2
void SnakeMove(pSnake ps)
{
    int x = 0;
    int y = 0;
    again:
    while (ps->_status == OK)
    {
        // 按键检测
        if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
        {
            ps->_dir = UP;
        }
        else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
        {
            ps->_dir = DOWN;
        }
        else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
        {
            ps->_dir = LEFT;
        }
        else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
        {
            ps->_dir = RIGHT;
        }
        else if (KEY_PRESS(VK_SPACE))
        {
            ps->_status = PAUSE;
        }
        else if (KEY_PRESS(VK_F3) && ps->_foodScore < 20)
        {
            ps->_sleepTime -= 30;
            ps->_foodScore += 2;
            // 更改单个食物分数
            SetPos(84, 5);
            printf("%2d", ps->_foodScore);
        }
        else if (KEY_PRESS(VK_F4) && ps->_foodScore > 2)
        {
            ps->_sleepTime += 30;
            ps->_foodScore -= 2;
            // 更改单个食物分数
            SetPos(84, 5);
            printf("%2d", ps->_foodScore);
        }
        else if (KEY_PRESS(VK_ESCAPE))
        {
            ps->_status = ESC;
        }

        // 设置下一个节点的x,y坐标
        switch (ps->_dir)
        {
            case UP:
                x = ps->_pSnake->x;
                y = ps->_pSnake->y - 1;
                break;
            case DOWN:
                x = ps->_pSnake->x;
                y = ps->_pSnake->y + 1;
                break;
            case LEFT:
                x = ps->_pSnake->x - 2;
                y = ps->_pSnake->y;
                break;
            case RIGHT:
                x = ps->_pSnake->x + 2;
                y = ps->_pSnake->y;
                break;
        }
        // 判断是否吃到食物
        if (ps->_pFood->x == x && ps->_pFood->y == y)
        {
            EatFood(ps);
        }
        else 
        {
            NotEatFood(ps, x, y);
        }
        // 撞墙检测
        if (IsKillByWall(ps))
        {
            ps->_status = KILL_BY_WALL;
        }
        // 撞自己检测
        if (IsKillBySelf(ps))
        {
            ps->_status = KILL_BY_SELF;
        }
        Sleep(ps->_sleepTime);
    }
    while (ps->_status == PAUSE)
    {
        if (KEY_PRESS(VK_SPACE))
        {
            ps->_status = OK;
        }
        goto again;
    }
    // 撞墙打印信息
    if (IsKillByWall(ps))
    {
        SetPos(45, 10);
        wprintf(L"墙墙被你撞西了,110把你抓走了");
    }
    // 撞自己打印信息
    if (IsKillBySelf(ps))
    {
        SetPos(45, 10);
        wprintf(L"自己撞自己了,120把你带走了");
    }
    return;
}

// 游戏前的初始化
void GameStart(pSnake ps)
{
    // 初始化控制台
    CmdInit();
    CursorHide();
    // 本地化配置
    setlocale(LC_ALL, "");
    WelcomeToGame();
    GameIntroduction();
    MapDraw();
    InitSnake(ps);
    CreatFood(ps);
}

// 游戏运行
void GameRun(pSnake ps)
{
    PrintStaticHelp();
    SnakeMove(ps);
    SetPos(45, 28);
    system("pause");
}

// 善后 释放蛇节点 以及食物节点
void GameEnd(pSnake ps)
{
    // 释放蛇节点
    pSnakeNode cur = NULL;
    while (ps->_pSnake != NULL)
    {
        cur = ps->_pSnake;
        ps->_pSnake = ps->_pSnake->next;
        free(cur);
    }
    // 释放食物节点
    free(ps->_pFood);
}